package sgf;

import java.util.*;

/**
 * Parses SGF files.
 * @author TKington
 *
 */
public class SGFParser {
	/**
	 * Parses the SGF Tags for a node, and stores them in the SGFNode object.
	 * @param s the SGF string for the node
	 * @param node the SGFNode the tags should be added to
	 * @throws SGFParseException if the SGF is illegal
	 */
    static void parseTags(String s, SGFNode node) throws SGFParseException {
        while(true) {
            s = s.trim();
            if(s.length() == 0)
                break;
            
            int index = 0;
            while(index < s.length() && Character.isLetter(s.charAt(index)))
                index++;
            
            if(index == 0)
                throw new SGFParseException("Illegal token: " + s);
            
            String tag = s.substring(0, index);
            //System.out.println("Tag: " + tag);
            
            ArrayList<String> propVals = new ArrayList<String>();
            while(index < s.length() && s.charAt(index) == '[') {
                index++;
                String val = ""; //$NON-NLS-1$
                int bdepth = 1;
                while(true) {
                    char c = s.charAt(index);
                    if(c == '[')
                        bdepth++;
                    else if(c == ']') {
                        bdepth--;
                        if(bdepth == 0)
                            break;
                    }
                    
                    val += c;
                    index++;
                }
                propVals.add(val);
                //System.out.println("PropVal: " + val);
                
                if(index >= s.length() || s.charAt(index) != ']')
                    throw new SGFParseException(s);
                index++;

                while(index < s.length() && Character.isWhitespace(s.charAt(index)))
                	index++;
            }
            
            node.addTag(tag, propVals);
            
            s = s.substring(index);
        }
    }
    
    /**
     * Takes a string and returns a string pair: the first token encountered
     * and the rest of the string.  We have to watch for tags like AW[dg][hg]
     * too.
     * @param s The string
     * @return the StringPair
     * @throws SGFParseException if the SGF string is illegal
     */
    // 
    static StringPair grabToken(String s) throws SGFParseException {
        int i;
        s = s.trim();
        String tok = ""; //$NON-NLS-1$
        int start = 0, end = 0;
        if (s.charAt(0) == '(') {
            // gotta find matching parenthesis
            int dpth = 1;
            start = 1;
            for (i=1;i<s.length();i++) {
                if (s.charAt(i) == '[') {// find matching bracket or throw
                    int bdepth = 1;
                    while(true) {
                        if(s.charAt(i) == '[')
                            bdepth++;
                        else if(s.charAt(i) == ']') {
                            bdepth--;
                            break;
                        }
                        i++;
                    }
                }
                if (s.charAt(i) == '(') dpth++;
                if (s.charAt(i) == ')') dpth--;
                if (dpth == 0)
                    break;
            }
            if (dpth != 0)
                throw new SGFParseException("Illegal token(bad depth):" + " " + s);
            end = i;
        }
        else if (s.charAt(0) == ';') {
            int dpth = 0;
            start = 1;
            for (i=1;i<s.length();i++) {
                if (s.charAt(i) == '[') {// find matching bracket or throw
                    int bdepth = 1;
                    while(true) {
                        if(s.charAt(i) == '[')
                            bdepth++;
                        else if(s.charAt(i) == ']') {
                            bdepth--;
                            break;
                        }
                        i++;
                    }
                }
                if ((s.charAt(i) == '(' || s.charAt(i) == ';'))
                    break;
            }
            if (dpth != 0)
                throw new SGFParseException("Illegal ; token" + " " + s);
            end = i;
        }
        else throw new SGFParseException("Illegal start of token: "
                                    + " " + s.charAt(0) + " " + "in" + " " + s);
        
        tok = s.substring(start, end);
        tok = tok.trim();
        s = s.substring(end, s.length());
        s = s.trim();
        if (s.length() > 0 && s.charAt(0) == ')')
            s = s.substring(1);
        return new StringPair(tok, s);
    }
    
    /**
     * Parses an SGF string representing a single node, and creates an SGFNode
     * representing that string.
     * @param sgf the SGF string
     * @param parent the parent of the new node
     * @return the new SGFNode
     * @throws SGFParseException
     */
    public static SGFNode parseNode(String sgf, SGFNode parent) throws SGFParseException {
        StringPair tok = grabToken(sgf);
        SGFNode node = new SGFNode(tok.a, parent);
        if (tok.b.length() > 0) {
            if (tok.b.charAt(0) == ';')
                node.addChild(parseNode(tok.b, node));
            else {
                while (tok.b.length() > 0) {
                    tok = grabToken(tok.b);
                    node.addChild(parseNode(tok.a, node));
                }
            }
        }
        return node;
    }
    
    /**
     * Trims junk off the front of an SGF file, until the first ( is found.
     * @param sgf the SGF
     * @return the trimmed file
     * @throws SGFParseException if the SGF is illegal
     */
    private static String trimComments(String sgf) throws SGFParseException {
        int i;
        outer:
        for(i = 0; i < sgf.length(); i++) {
            if(sgf.charAt(i) == '(') {
                for(int j = i + 1; j < sgf.length(); j++) {
                    char c = sgf.charAt(j);
                    if(c == ';')
                        break outer;
                    if(!Character.isWhitespace(c))
                        continue outer;
                }
            }
        }
        if(i == sgf.length())
            throw new SGFParseException("No ( found");
        return sgf.substring(i);
    }
    
    /**
     * Parses an SGF file, and returns a list of SGFNodes that represent the game
     * trees found in the file.
     * @param sgf the SGF file
     * @return a list of SGFNodes
     * @throws SGFParseException
     */
    public static ArrayList parse(String sgf) throws SGFParseException {
        ArrayList<SGFNode> ret = new ArrayList<SGFNode>();
        sgf = sgf.trim();
        sgf = trimComments(sgf);
        
        StringPair tok = grabToken(sgf);
        while(true) {
            ret.add(parseNode(tok.a, null));
            
            if(tok.b.length() == 0)
                break;
            
            tok = grabToken(tok.b);
        }
        
        return ret;
    }
    
    /**
     * Class to hold a pair of strings. Used to hold the text of the current
     * node, and the rest of the SGF file.
     */
    static class StringPair {
    	/** First string */
        String a;
        /** Second string */
        String b;
        
        /**
         * Creates a StringPair.
         * @param a the first string
         * @param b the second string
         */
        StringPair(String a, String b) {
            this.a = a;
            this.b = b;
        }
    }
}
